/*
 * Copyright (c) 2010 Mathew Hall, University of Sheffield.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following conditions
 * are met:
 *
 * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided
 * with the distribution.
 *
 * Neither the name of the University of Sheffield nor the names of its
 * contributors may be used to endorse or promote products derived
 * from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
package search.fitnessfunctions;

import gpinterpreter.stack.StackInstruction;
import gpinterpreter.stack.StackInterpreter;
import gpinterpreter.vector.VecInterpreter;
import gpinterpreter.vector.VecInstruction;

import java.util.ArrayList;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jgap.FitnessFunction;
import org.jgap.Gene;
import org.jgap.IChromosome;
import org.jgap.impl.FixedBinaryGene;
import org.jgap.impl.IntegerGene;

import primitives.cluster.ClusterHead;
import primitives.cluster.ClusterNode;
import primitives.cluster.ClusterUtils;
import primitives.cluster.IClusterLevel;
import primitives.graph.Node;
import search.fitnessfunctions.util.TreeTranslator;
import search.genes.*;

public abstract class TreeFitnessFunction extends FitnessFunction {

    /**
     *
     */
    private static final long serialVersionUID = -5219003121816689020L;
    private ClusterHead tree;

    public void setTree(ClusterHead tree) {
        this.tree = tree;
    }
    private int evaluations = 0;
    private int fitnessBudget = -1;

    public void setFitnessBudget(int fb) {
        fitnessBudget = fb;
    }

    public boolean budgetHit() {
        return evaluations >= fitnessBudget;
    }
    private ClusterHead best = null;

    public ClusterHead getFittest() {
        return best;
    }
    double bestFitness = -1;

    public double getBestFitness() {
        return bestFitness;
    }
    private int evalsToBest = -1;

    public int getEvalsToBest() {
        return evalsToBest;
    }

    protected boolean punishesWastage() {
        return false;
    }

    /**
     * @return true if the fitness function requires clusters to be subsumed
     * (with EdgeSubsumptionTransformer)
     *
     */
    public static Boolean subsumes() {
        return false;
    }

    public int getFitnessEvaluations() {
        return evaluations;
    }

    public double scaleWastage(double fitness, int amount) {
        return fitness;
    }

    public double evaluate(IChromosome a_subject) {

        if (a_subject.size() == 0) {
            return 0.0;
        }
        GeneType type = (GeneType) a_subject.getApplicationData();

        ClusterHead ch = TreeTranslator.getTree(a_subject, tree);

        double fitness;

        if (type == GeneType.STACK) {
            ArrayList<StackInstructionGene> prog = new ArrayList<StackInstructionGene>();

            for (int i = 0; i < a_subject.size(); i++) {
                prog.add((StackInstructionGene) a_subject.getGene(i));
            }

            fitness = evaluateStackGene(prog);

        } else {
            fitness = evaluate(ch);
        }

        checkBudget(fitness, ch);
        return fitness;
    }

    public void checkBudget(double fitness, ClusterHead solution) {
        evaluations++;
        if (!budgetHit()) {
            evalsToBest = evaluations;
            if (fitness > bestFitness) {
                bestFitness = fitness;
                best = solution.deepCopy();
                evalsToBest = evaluations;
            }
        }
    }
    
    public double evaluateTree(ClusterHead tr){
        double fitness = evaluate(tr);
        checkBudget(fitness, tr);
        return fitness;
        
    }

    public double evaluateStackGene(ArrayList<StackInstructionGene> program) {
        ArrayList<StackInstruction> p = new ArrayList<StackInstruction>();

        for (int i = 0; i < program.size(); i++) {
            p.add(program.get(i).getInstruction());
        }


        StackInterpreter interp = new StackInterpreter(p, tree.deepCopy());


        ClusterHead evaluated = interp.run();
        double fitness = evaluate(evaluated);
        if (punishesWastage()) {
            fitness = scaleWastage(fitness, interp.getWastage());
        }


        checkBudget(fitness, evaluated);

        return fitness;

    }

    public abstract double evaluate(ClusterHead result);
}
